/* vim: set ts=2 et sw=2 cindent fo=qroca: */

package com.globant.google.mendoza;

import java.util.Date;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.globant.google.mendoza.malbec.OrderListener;
import com.globant.google.mendoza.malbec.Order;

import com.globant.google.mendoza.malbec.schema._2.NewOrderNotification;
import com.globant.google.mendoza.malbec.schema._2.RiskInformationNotification;
import com.globant.google.mendoza.malbec.schema._2.Money;
import com.globant.google.mendoza.malbec.schema._2.
AuthorizationAmountNotification;

import com.globant.google.mendoza.malbec.transport.Transport;
import com.globant.google.mendoza.malbec.transport.Receiver;

/** Implements an intercepting proxy for a transport delegate.
 *
 * This proxy intercepts the messages received by the wrapped transport and
 * redirects them to another server.
 */
public final class TransportProxy implements Transport, OrderListener {

  /** The class logger.
   */
  private static Log log = LogFactory.getLog(TransportProxy.class);

  /** The transport implementation that proxy delegates the requests to.
   */
  private Transport delegate;

  /** The server that will be notified when a notification
   * message is received.
   */
  private MendozaServer mendozaServer;

  /** The merchant app URL.
   */
  private String merchantURL;

  /** The Checkout server post URL.
   */
  private String postURL;

  /** Builds a transport proxy.
   *
   * @param transport The object that this proxy delegates the requests
   * to/from. It cannot be null.
   *
   * @param server The server that will be notified when a new order
   * notification message is received. It cannot be null.
   *
   * @param theMerchantURL The merchant app URL. It cannot be null.
   *
   * @param thePostURL he Checkout server post URL. It cannot be null.
   */
  TransportProxy(final Transport transport, final MendozaServer server,
      final String theMerchantURL, final String thePostURL) {
    if (transport == null) {
      throw new IllegalArgumentException("The transport cannot be null");
    }
    if (server == null) {
      throw new IllegalArgumentException("The server cannot be null");
    }
    if (theMerchantURL == null) {
      throw new IllegalArgumentException("The merchant URL cannot be null");
    }
    if (thePostURL == null) {
      throw new IllegalArgumentException("The Checkout URL cannot be null");
    }
    delegate = transport;
    mendozaServer = server;
    postURL = thePostURL;
    merchantURL = theMerchantURL;
  }

  /** Sends a message to the server.
   *
   * This implementation throws an error.
   *
   * @param message The message to send to the server. It cannot be null.
   *
   * @return Returns the server response, usually an acknowledge or an error
   * related to the validity of the message structure.
   */
  public String send(final String message) {
    this.setServerURL(postURL);
    String serverResponse = delegate.send(message);
    this.setServerURL(merchantURL);
    return serverResponse;
  }

  /** Registers a receiver for this transport.
   *
   * Registers a receiver that forwards the message to another server.
   *
   * @param receiver The receiver that is notified of the message availability.
   */
  public void registerReceiver(final Receiver receiver) {
    Receiver receiverInterceptor = new Receiver() {

      public String receive(final String uri, final String method, final
          Properties header, final Properties params, final String message) {
        log.trace("Entering receive");
        String response = delegate.send(message);
        log.trace("Leaving receive");
        return response;
      }

      public void received(final String message) {
        log.trace("Entering received");
        if (log.isDebugEnabled()) {
          log.debug("Message: " + message);
        }
       if (message.contains("<merchant-calculation-callback")) {
          log.debug("Merchant calculation callback received.");
          Date start = new Date();
          String response = delegate.send(message);
          Date end = new Date();
          mendozaServer.setMerchantCalculation(message, response, start, end);
          receiver.received(message);
       } else {
         delegate.send(message);
         receiver.received(message);
       }
       log.trace("Leaving received");
     }
  };
 delegate.registerReceiver(receiverInterceptor);
 }

  /** Starts listening to requests.
   *
   * You must have registered a receiver before calling start. The registered
   * receiver is notified of new requests.
   *
   * This implementation simply calls start on the delegate.
   */
  public void start() {
    delegate.start();
  }

  /** Stops the server.
   *
   * This implementation simply calls stop on the delegate.
   */
  public void stop() {
    delegate.stop();
  }

  /** Called when a new message is received, before the acknowledge is sent to
   * GBuy.
   *
   * @param command The command name. This is the top level xml node name.
   *
   * @param orderNumber The GBuy order number.
   *
   * @param timestamp The notification timestamp.
   */
  public void receiveMessage(final String command, final String orderNumber,
      final Date timestamp) {
    log.trace("Entering receiveMessage('" + command + "', '" + orderNumber
        + "', ...)");
  }

  /** Called when a buyer places an order.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param newOrderNotification The new order message included in the new
   * order notification.
   */
  public synchronized void newOrder(final Order order, final Date timestamp,
      final NewOrderNotification newOrderNotification) {
    log.trace("Entering newOrder");
    mendozaServer.registerNewOrderNotification(
        order.getNumber(), newOrderNotification);
    mendozaServer.setCurrentOrder(order);
    log.trace("Leaving newOrder");
  }

  /** Called when GBuy sends the risk information notification.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param information The buyer's risk information.
   */
  public void riskInformation(final Order order, final Date timestamp, final
      RiskInformationNotification information) {
    log.trace("Entering riskInformation");
    mendozaServer.registerRiskInformationNotification(
        order.getNumber(), information);
    log.trace("Leaving riskInformation");
  }

  /** Called when the GBuy order changes state.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param newFulfillmentState Order's current fulfillment state.
   *
   * @param newFinancialState Order's current financial state.
   *
   * @param oldFulfillmentState Fulfillment state before this state change.
   *
   * @param oldFinancialState Financial state before this state change.
   *
   * @param reason Description of state change (not always used).
   */
  public void stateChanged(final Order order, final Date timestamp, final
      String newFulfillmentState, final String newFinancialState, final String
      oldFulfillmentState, final String oldFinancialState,
      final String reason) {
    log.trace("Entering stateChanged");
    mendozaServer.registerOrderStateChanged(order.getNumber(),
        newFulfillmentState, newFinancialState);
    log.trace("Leaving stateChanged");
  }

  /** Called when all or part of the funds for the order are captured
   * (charged).
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param latest Value of the most recent charge on this order.
   *
   * @param total Total value charged on this order.
   */
  public void chargeAmount(final Order order, final Date timestamp, final Money
      latest, final Money total) {
    log.trace("Entering chargeAmount");
    log.trace("Leaving chargeAmount");
  }

  /** When the buyer initiates a chargeback against the order, and GBuy
   * approves the chargeback, GBuy sends you a chargeback-amount-notification.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param latest Value of the most recent charge on this order.
   *
   * @param total Total value charged on this order.
   */
  public void chargebackAmount(final Order order, final Date timestamp, final
      Money latest, final Money total) {
    log.trace("Entering chargebackAmount");
    log.trace("Leaving chargebackAmount");
  }

  /** When you request a refund by sending a refund-order message, GBuy sends
   * you a refund-amount-notification.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param latest Value of the most recent charge on this order.
   *
   * @param total Total value charged on this order.
   */
  public void refundAmount(final Order order, final Date timestamp, final Money
      latest, final Money total) {
    log.trace("Entering refundAmount");
    log.trace("Leaving refundAmount");
  }

  /** Authorization Amount Notification.
   *
   * @param order The order initialized to talk to GBuy.
   *
   * @param timestamp Time and date of message in ISO 8601 format.
   *
   * @param authorizationAmountNotification The authorization amount
   * notification.
   */
  public void authorizationAmount(final Order order, final Date timestamp,
      final AuthorizationAmountNotification authorizationAmountNotification) {
    log.trace("Entering authorizationAmount");
    log.trace("Leaving authorizationAmount");
  }

  /** Called when an unrecognized message is received.
   *
   * @param message The complete received message.
   */
  public void unknown(final String message) {
    log.error("Unknown message type: \n" + message);
  }

  /** Sets the url to use to send messages to GBuy.
   *
   * @param url The url. Cannot be null.
   */
  public void setServerURL(final String url) {
    delegate.setServerURL(url);
  }
}

